open Constant

let opt_map f = function Some x -> Some (f x) | None -> None
let opt f default = function Some x -> f x | None -> default
let opt_iter f = function Some x -> f x | None -> ()




let generate_thumbname = 
	let count = ref 0 in 
	fun tempDir ->
		let rec loop () = 
			let basename = Printf.sprintf "thumb%03d.jpg" !count in
			let filename = Filename.concat tempDir basename in
			incr count;
			if Sys.file_exists filename then loop() else filename in
	  loop()

let generate_filename = 
	let count = ref 0 in 
	fun tempDir ->
		let rec loop () = 
			let basename = Printf.sprintf "img%03d.jpg" !count in
			let filename = Filename.concat tempDir basename in
			incr count;
			if Sys.file_exists filename then loop() else filename in
	  loop()

let rotate tempDir side filename =
	let aux = Filename.concat tempDir "auxRot.jpg" in
	let angle = if side = LEFT then "270" else "90" in
	if (Sys.command ("jpegtran -outfile " ^ aux ^ " -rotate " ^ angle ^ " " ^ filename) <> 0) or
	   (Sys.command ("mv -f " ^ aux ^ " " ^ filename) <> 0) 
	then failwith "Rotation failed"
	
let check_downloaded pages =
	let callback _  = function Some page -> if page.loaded = None then raise Exit | None -> () in
	begin try EArray.iterate pages callback ; true
	with Exit -> false end




let label_text i = if i = -1 then "No Page" else Printf.sprintf "Page %d" i

class model = fun tempDir ->
	let camera1, camera2 = Camera.configure_cameras() in
	let pages = EArray.create None in
	let dbname = Filename.concat tempDir "book.db" in 	
	let auxFile = Filename.concat tempDir "aux.jpg" in
	let db = Database.open_db dbname pages in
	let take_photo current camera = 
		let thumbname = generate_thumbname tempDir in 
		let page = camera#take_photo thumbname in
		EArray.set pages current (Some page) in
	let rotate_left = rotate tempDir LEFT in
	let rotate_right = rotate tempDir RIGHT in
	let generate_pagename () = generate_filename tempDir in
  let _ = 
		 opt_iter  (fun c -> c#set_rotate rotate_left) camera1;
		 opt_iter  (fun c -> c#set_rotate rotate_right) camera2 in
	object(self)
		val mutable c_left = camera1
		val mutable c_right = camera2

		 
		val mutable current = 0
		val pages = pages
		val db = db

		method camera_left = opt (fun c -> c#name) "No camera" c_left 
		method camera_right = opt  (fun c -> c#name) "No camera" c_right
				
		method swap_cameras = 
			let aux = c_left in c_left <- c_right; c_right <- aux;
			opt_iter (fun c -> c#set_rotate rotate_left) c_left;
			opt_iter (fun c -> c#set_rotate rotate_right) c_right
					
		method take_photo_left =
			if (current = EArray.get_size pages) then failwith "at_end";
			opt_iter (fun c -> take_photo current c) c_left  

		method take_photo_right =
			if (current = EArray.get_size pages) then failwith "at_end";
			opt_iter (fun c -> take_photo (current + 1) c) c_right 

		method take_both =
			let at_end = (current = EArray.get_size pages) in
			if at_end then begin EArray.insert pages current; EArray.insert pages current end;
			self#take_photo_left ; self#take_photo_right;
			if at_end then current <- current + 2
					
		method get i =
			let image_opt = 
				match EArray.get pages i with
				| None -> None
				| Some { loaded = Some loadedname } -> Some loadedname
				| Some p -> Some p.thumbname in 
			(label_text i, image_opt)
		
		method left =
		 if current = EArray.get_size pages then
			  if current = 0 then (label_text 0,None) else self#get(current - 2)
		 else self#get current
		method right =
			if current = EArray.get_size pages then
			  if current = 0 then (label_text 1, None) else self#get(current - 1)
			else self#get (current + 1)
			
		method focus_left value = opt_map (fun c -> c#set_focus value; c#take_thumb auxFile) c_left
		
		method focus_right value = opt_map (fun c -> c#set_focus value; c#take_thumb auxFile) c_right

		method get_focus_left = opt_map (fun c -> let f = c#get_focus and (m1,m2,s) = c#get_focus_range in f,m1,m2,s) c_left
		method get_focus_right = opt_map (fun c -> let f = c#get_focus and (m1,m2,s) = c#get_focus_range in f,m1,m2,s) c_right

		method next = if current < EArray.get_size pages then current <- current + 2
		method previous = if current > 0 then current <- current - 2
		method to_start = current <- 0
		method to_end = current <- EArray.get_size pages
		method position = (current, EArray.get_size pages)
		
		method save = 
			Database.begin_transaction db;
			Database.save_db db pages;
		  Database.set_config db _CAMERAS_KEY (self#camera_left ^ self#camera_right);
			opt_iter (fun c -> Database.set_config_float db _FOCUS_LEFT_KEY  (c#get_focus)) c_left;
			opt_iter (fun c -> Database.set_config_float db _FOCUS_RIGHT_KEY (c#get_focus)) c_right;
			Database.end_transaction db
		method insert = EArray.insert pages current; EArray.insert pages current
		method delete = 
			if current < EArray.get_size pages - 1 then begin
				EArray.delete pages current; EArray.delete pages current; true
			end else false
		
		method wipe_all =
			if not (check_downloaded pages) & false then false
			else begin 
				opt_iter (fun c -> c#wipe_all) c_left;
				opt_iter (fun c -> c#wipe_all) c_right; 
				true
			end 
				
		method download_all (progress : int -> int -> (unit -> unit) -> unit) final_kont = 
			let ok = ref 0 in
			let length = EArray.get_size pages in
			let rec loop i nok =
				let callback () = 
				  if i = length then begin
						for i = 0 to length - 1 do
							let page_file = Filename.concat tempDir (Printf.sprintf "page%04d.jpg" i) in
							if Sys.file_exists page_file then Unix.unlink page_file;
							match EArray.get pages i with
							| Some { loaded = Some path } -> Unix.link path page_file
							| _ -> () 
						done;
						final_kont nok
				  end else begin
					  let error = 
						  begin match EArray.get pages i with
						  | (Some page) -> 
							  	begin try
								  	if (i mod 2 = 0) 
									  then opt_iter (fun c -> c#load_page generate_pagename page) c_left
										else opt_iter (fun c -> c#load_page generate_pagename page) c_right;
									with Failure f -> () end;
									if page.loaded = None then 1 else 0; 
							| None -> 0
						  end in
						  loop (i+1) (nok + error)
				  end in
			  progress i length callback in
			loop 0 0
		initializer
			let check_reverse camera_dsc =
				 let fgp_left =  opt (fun c -> c#digest) "-" c_left in
				 let fgp_right = opt (fun c -> c#digest) "-" c_right in
			   if (fgp_left ^ fgp_right <> camera_dsc & fgp_right ^ fgp_left = camera_dsc) then self#swap_cameras in
			opt_iter (check_reverse ) (Database.get_config db _CAMERAS_KEY);
			opt_iter (fun v -> opt_iter (fun c -> c#set_focus v) c_left) (Database.get_config_float db _FOCUS_LEFT_KEY);
			opt_iter (fun v -> opt_iter (fun c -> c#set_focus v) c_right) (Database.get_config_float db _FOCUS_RIGHT_KEY);
	end
	